package tree;

import java.awt.Toolkit;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;

import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.tree.DefaultMutableTreeNode;

import sgf.SGFParseException;
import sgf.SGFParser;
import ui.EditingSGFDialog;
import ui.GobanPanel;
import ui.Main;
import util.GS;
import util.SimpleFileFilter;
import util.Util;

import board.Board;

/** 
 * Represents a page.  There can be different types of pages, and the
 * differences are handled by having different IDisplayStrategy subclasses.
 * @author TKington
 *
 */
public class PageNode extends DefaultMutableTreeNode implements Transferable {
	/** The pop-up menu for PageNodes. */
	private static JPopupMenu popupMenu;
	/** The next available page id. */
	private static int nextID = 0;
	
	/** The board on this page. */
	private Board board;
	/** The unique id. */
	private int id;
	/** The display strategy. */
	private IDisplayStrategy displayStrategy;
	
	/**
	 * Sets the pop-up menu for PageNodes.
	 * @param menu the menu
	 */
	public static void setPopupMenu(JPopupMenu menu) {
		popupMenu = menu;
	}
	
	/**
	 * Creates a new PageNode
	 * @param title the title
	 */
	public PageNode(String title) {
		super(title);
		
		board = new Board(19);
		id = nextID++;
		displayStrategy = new DiagramDisplayStrategy("");
	}

	/**
	 * Creates a new PageNode by copying an existing PageNode.
	 * @param n the node to be copied
	 */
	public PageNode(PageNode n) {
		super(n.getUserObject());
		board = new Board(n.board);
		id = nextID++;
		
		try {
			displayStrategy = n.displayStrategy.clone();
		} catch (CloneNotSupportedException e) {
			Util.log(e);
		}
	}
	
	/**
	 * This sets the id of the current node, but more importantly sets the nextID
	 * to be higher than the current id.  This is important when a file is loaded -
	 * each page will already have an id, and we don't want to assign new pages
	 * non-unique ids.
	 * @param id the id
	 */
	private void setID(int id) {
		this.id = id;
		nextID = Math.max(nextID, id + 1);
	}

	/**
	 * Displays this PageNode.
	 * @param goban the GobanPanel
	 */
	public void display(GobanPanel goban) {
    	goban.setBoard(board);
    	board.setPanel(goban);
		displayStrategy.display(this, goban, board);
	}
	
	/**
	 * Called when the text pane mode has changed.
	 * @param mode the new mode
	 */
	public void setTextPaneMode(int mode) {
		displayStrategy.setTextPaneMode(mode);
	}
	
	/**
	 * Called when user is leaving this page.
	 *
	 */
	public void leavingPage() {
		displayStrategy.leavingPage();
	}
	
	/**
	 * Called when board size changes.
	 * @param size the new size
	 */
	public void setBoardSize(int size) {
		board = new Board(size);
	}
	
	/** Creates an empty problem for this page. */
	public void newSGF() {
		setSGF("(;GM[1]FF[4]AP[GoViewer]SZ[19])");
	}

	/**
	 * Imports an SGF file to be used as the problem for this page.
	 * @param sgfFile the SGF file
	 */
	public void importSGF(File sgfFile) {
		if(sgfFile == null) {
			SimpleFileFilter sgfFilter = new SimpleFileFilter("SGF Files", "sgf");
			String filename = Util.chooseFile(Main.frame, sgfFilter, "Import SGF File", false, "Import");
			if(filename == null)
				return;

			sgfFile = new File(filename);
		}
		
		if(!sgfFile.exists())
			return;
		
		BufferedReader in = null;
		StringBuffer sgf = new StringBuffer();
		try {
			in = new BufferedReader(new FileReader(sgfFile));
			String line;
			while((line = in.readLine()) != null)
				sgf.append(line);
			
		}
		catch(IOException e) {
			JOptionPane.showMessageDialog(Main.frame, "Error reading file.  See viewer.log for details");
			Util.logSilent(e);
			return;
		}
		finally {
			if(in != null) {
				try {
					in.close();
				}
				catch(IOException e) {
					Util.log(e);
				}
			}
		}
		
		setSGF(sgf.toString());
	}

	/**
	 * Sets the SGF problem for this page if it parses correctly.
	 * @param sgf the SGF
	 */
	private void setSGF(String sgf) {
		try {
			ArrayList recs = SGFParser.parse(sgf);
			
	        if(recs.size() > 1) {
	        	JOptionPane.showMessageDialog(Main.frame, 
	        			"This file contains more than one game record.");
	        	return;
	        }
		}
		catch(SGFParseException e) {
            String msg = e.getMessage();
            if(msg.length() > 50)
                msg = msg.substring(0, 50) + "...";
            JOptionPane.showMessageDialog(Main.frame, "Error parsing problem:\n" + msg);
            Util.logSilent(e);
            return;
		}
		
		IDisplayStrategy oldStrat = displayStrategy;
		displayStrategy = new SGFDisplayStrategy(sgf.toString());
		
		if(!oldStrat.equals(displayStrategy)) {
			Main.frame.setDirty(true);
			Main.frame.gotoPage(this, false);
		}
	}
	
	/**
	 * Exports the SGF from this page to a file.
	 * @param file the file
	 * @return true if successful
	 */
	public boolean exportSGF(File file) {
		return ((SGFDisplayStrategy)displayStrategy).exportSGF(file);
	}
	
	/**
	 * Saves the SGF for this page to a temporary file, starts
	 * the configured editor, waits until the editor is closed,
	 * and imports the changed SGF.
	 *
	 */
	public void editSGF() {
		File tempFile;
		try {
			tempFile = File.createTempFile("GoViewer", ".sgf");
		} catch (IOException e) {
			JOptionPane.showMessageDialog(Main.frame, "Couldn't create temporary file. See viewer.log for details.");
			Util.logSilent(e);
			return;
		}
		
		if(!exportSGF(tempFile)) {
			Toolkit.getDefaultToolkit().beep();
			return;
		}
		
		File editor = GS.getSGFEditor();
    	if(editor == null) {
    		JOptionPane.showMessageDialog(Main.frame, "First configure an SGF editor in the preferences");
    		return;
    	}
    	
    	if(!editor.exists()) {
    		JOptionPane.showMessageDialog(Main.frame, "The SGF editor " + editor.getPath() + " does not exist");
    		return;
    	}
    	
    	File dir = editor.getParentFile();
    	String [] args = new String[2];
    	args[0] = editor.getAbsolutePath();
    	args[1] = tempFile.getAbsolutePath();
    	
    	try {
    		Process editProc = Runtime.getRuntime().exec(args, null, dir);
    		new EditingSGFDialog(Main.frame, editProc);
    	}
    	catch(IOException e) {
    		JOptionPane.showMessageDialog(Main.frame, "Error launching editor. See viewer.log for details.");
    		Util.logSilent(e);
    	}
    	
    	importSGF(tempFile);
    	if(!tempFile.delete()) {
    		JOptionPane.showMessageDialog(Main.frame, "Error deleting file " + tempFile.getPath());
    	}
	}
	
	/**
	 * Clears the SGF problem on this page.
	 *
	 */
	public void clearSGF() {
		displayStrategy = new DiagramDisplayStrategy("");
	}

	/** File format version. */
	private static int REVISION = 1;
	
	/**
	 * Loads a PageNode from a stream.
	 * @param in the input stream
	 * @return the new PageNode object
	 * @throws IOException if there is a problem reading from the stream
	 * @throws ClassNotFoundException if there is a problem with the file format
	 */
	public static PageNode readFromFile(ObjectInputStream in)
								throws IOException, ClassNotFoundException {
		int revision = in.readInt();
		if(revision > REVISION)
			throw new IOException("File is newer than program");
		
		int id = in.readInt();
		String title = (String)in.readObject();
		Board board = Board.readFromFile(in);
		IDisplayStrategy ds = (IDisplayStrategy)in.readObject();
		
		PageNode page = new PageNode(title);
		page.setID(id);
		page.board = board;
		page.displayStrategy = ds;
		
		int numChildren = in.readInt();
		for(int i = 0; i < numChildren; i++) {
			PageNode ch = PageNode.readFromFile(in);
			page.add(ch);
		}
		
		return page;
	}
	
	/**
	 * Writes a PageNode to a stream.
	 * @param out the output stream
	 * @throws IOException if there is a problem writing to the stream
	 */
	public void writeToFile(ObjectOutputStream out) throws IOException {
		out.writeInt(REVISION);
		out.writeInt(id);
		out.writeObject(getUserObject());
		board.writeToFile(out);
		out.writeObject(displayStrategy);
		
		int numChildren = getChildCount();
		out.writeInt(numChildren);
		for(int i = 0; i < numChildren; i++) {
			PageNode p = (PageNode)getChildAt(i);
			p.writeToFile(out);
		}
	}
	
	/**
	 * Returns the id of this PageNode.
	 * @return the id
	 */
	public int getID() { return id; }

	/**
	 * Returns the pop-up menu for this PageNode.
	 * @return the pop-up menu
	 */
	public JPopupMenu getPopupMenu() {
		return popupMenu;
	}

	//	Drag and drop stuff
	
	/** The DataFlavor for dragged PageNodes. */
	public static DataFlavor pageNodeFlavor;
	
	static {
		try {
			pageNodeFlavor = 
				new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType 
					+ ";class=tree.PageNode");
		}
		catch(ClassNotFoundException e) {
			Util.log(e);
		}
	}
	
	public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
		if(!flavor.equals(pageNodeFlavor))
			throw new UnsupportedFlavorException(flavor);
		
		return this;
	}

	public DataFlavor[] getTransferDataFlavors() {
		return new DataFlavor [] { pageNodeFlavor };
	}

	public boolean isDataFlavorSupported(DataFlavor flavor) {
		return flavor.equals(pageNodeFlavor);
	}
}
